/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.security.auth.module;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.*;
import javax.security.auth.Destroyable;
import javax.security.auth.DestroyFailedException;
import javax.security.auth.Subject;
import javax.security.auth.x500.*;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.ConfirmationCallback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

import sun.security.util.Password;

/**
 * Provides a JAAS login module that prompts for a key store alias and
 * populates the subject with the alias's principal and credentials. Stores
 * an <code>X500Principal</code> for the subject distinguished name of the
 * first certificate in the alias's credentials in the subject's principals,
 * the alias's certificate path in the subject's public credentials, and a
 * <code>X500PrivateCredential</code> whose certificate is the first
 * certificate in the alias's certificate path and whose private key is the
 * alias's private key in the subject's private credentials. <p>
 *
 * Recognizes the following options in the configuration file:
 * <dl>
 *
 * <dt> <code>keyStoreURL</code> </dt>
 * <dd> A URL that specifies the location of the key store.  Defaults to
 * a URL pointing to the .keystore file in the directory specified by the
 * <code>user.home</code> system property.  The input stream from this
 * URL is passed to the <code>KeyStore.load</code> method.
 * "NONE" may be specified if a <code>null</code> stream must be
 * passed to the <code>KeyStore.load</code> method.
 * "NONE" should be specified if the KeyStore resides
 * on a hardware token device, for example.</dd>
 *
 * <dt> <code>keyStoreType</code> </dt>
 * <dd> The key store type.  If not specified, defaults to the result of
 * calling <code>KeyStore.getDefaultType()</code>.
 * If the type is "PKCS11", then keyStoreURL must be "NONE"
 * and privateKeyPasswordURL must not be specified.</dd>
 *
 * <dt> <code>keyStoreProvider</code> </dt>
 * <dd> The key store provider.  If not specified, uses the standard search
 * order to find the provider. </dd>
 *
 * <dt> <code>keyStoreAlias</code> </dt>
 * <dd> The alias in the key store to login as.  Required when no callback
 * handler is provided.  No default value. </dd>
 *
 * <dt> <code>keyStorePasswordURL</code> </dt>
 * <dd> A URL that specifies the location of the key store password.  Required
 * when no callback handler is provided and
 * <code>protected</code> is false.
 * No default value. </dd>
 *
 * <dt> <code>privateKeyPasswordURL</code> </dt>
 * <dd> A URL that specifies the location of the specific private key password
 * needed to access the private key for this alias.
 * The keystore password
 * is used if this value is needed and not specified. </dd>
 *
 * <dt> <code>protected</code> </dt>
 * <dd> This value should be set to "true" if the KeyStore
 * has a separate, protected authentication path
 * (for example, a dedicated PIN-pad attached to a smart card).
 * Defaults to "false". If "true" keyStorePasswordURL and
 * privateKeyPasswordURL must not be specified.</dd>
 *
 * </dl>
 */
@jdk.Exported
public class KeyStoreLoginModule implements LoginModule {

  private static final ResourceBundle rb = AccessController.doPrivileged(
      new PrivilegedAction<ResourceBundle>() {
        public ResourceBundle run() {
          return ResourceBundle.getBundle(
              "sun.security.util.AuthResources");
        }
      }
  );

    /* -- Fields -- */

  private static final int UNINITIALIZED = 0;
  private static final int INITIALIZED = 1;
  private static final int AUTHENTICATED = 2;
  private static final int LOGGED_IN = 3;

  private static final int PROTECTED_PATH = 0;
  private static final int TOKEN = 1;
  private static final int NORMAL = 2;

  private static final String NONE = "NONE";
  private static final String P11KEYSTORE = "PKCS11";

  private static final TextOutputCallback bannerCallback =
      new TextOutputCallback
          (TextOutputCallback.INFORMATION,
              rb.getString("Please.enter.keystore.information"));
  private final ConfirmationCallback confirmationCallback =
      new ConfirmationCallback
          (ConfirmationCallback.INFORMATION,
              ConfirmationCallback.OK_CANCEL_OPTION,
              ConfirmationCallback.OK);

  private Subject subject;
  private CallbackHandler callbackHandler;
  private Map<String, Object> sharedState;
  private Map<String, ?> options;

  private char[] keyStorePassword;
  private char[] privateKeyPassword;
  private KeyStore keyStore;

  private String keyStoreURL;
  private String keyStoreType;
  private String keyStoreProvider;
  private String keyStoreAlias;
  private String keyStorePasswordURL;
  private String privateKeyPasswordURL;
  private boolean debug;
  private javax.security.auth.x500.X500Principal principal;
  private Certificate[] fromKeyStore;
  private java.security.cert.CertPath certP = null;
  private X500PrivateCredential privateCredential;
  private int status = UNINITIALIZED;
  private boolean nullStream = false;
  private boolean token = false;
  private boolean protectedPath = false;

    /* -- Methods -- */

  /**
   * Initialize this <code>LoginModule</code>.
   *
   * <p>
   *
   * @param subject the <code>Subject</code> to be authenticated. <p>
   * @param callbackHandler a <code>CallbackHandler</code> for communicating with the end user
   * (prompting for usernames and passwords, for example), which may be <code>null</code>. <p>
   * @param sharedState shared <code>LoginModule</code> state. <p>
   * @param options options specified in the login <code>Configuration</code> for this particular
   * <code>LoginModule</code>.
   */
  // Unchecked warning from (Map<String, Object>)sharedState is safe
  // since javax.security.auth.login.LoginContext passes a raw HashMap.
  @SuppressWarnings("unchecked")
  public void initialize(Subject subject,
      CallbackHandler callbackHandler,
      Map<String, ?> sharedState,
      Map<String, ?> options) {
    this.subject = subject;
    this.callbackHandler = callbackHandler;
    this.sharedState = (Map<String, Object>) sharedState;
    this.options = options;

    processOptions();
    status = INITIALIZED;
  }

  private void processOptions() {
    keyStoreURL = (String) options.get("keyStoreURL");
    if (keyStoreURL == null) {
      keyStoreURL =
          "file:" +
              System.getProperty("user.home").replace(
                  File.separatorChar, '/') +
              '/' + ".keystore";
    } else if (NONE.equals(keyStoreURL)) {
      nullStream = true;
    }
    keyStoreType = (String) options.get("keyStoreType");
    if (keyStoreType == null) {
      keyStoreType = KeyStore.getDefaultType();
    }
    if (P11KEYSTORE.equalsIgnoreCase(keyStoreType)) {
      token = true;
    }

    keyStoreProvider = (String) options.get("keyStoreProvider");

    keyStoreAlias = (String) options.get("keyStoreAlias");

    keyStorePasswordURL = (String) options.get("keyStorePasswordURL");

    privateKeyPasswordURL = (String) options.get("privateKeyPasswordURL");

    protectedPath = "true".equalsIgnoreCase((String) options.get
        ("protected"));

    debug = "true".equalsIgnoreCase((String) options.get("debug"));
    if (debug) {
      debugPrint(null);
      debugPrint("keyStoreURL=" + keyStoreURL);
      debugPrint("keyStoreType=" + keyStoreType);
      debugPrint("keyStoreProvider=" + keyStoreProvider);
      debugPrint("keyStoreAlias=" + keyStoreAlias);
      debugPrint("keyStorePasswordURL=" + keyStorePasswordURL);
      debugPrint("privateKeyPasswordURL=" + privateKeyPasswordURL);
      debugPrint("protectedPath=" + protectedPath);
      debugPrint(null);
    }
  }

  /**
   * Authenticate the user.
   *
   * <p> Get the Keystore alias and relevant passwords.
   * Retrieve the alias's principal and credentials from the Keystore.
   *
   * <p>
   *
   * @return true in all cases (this <code>LoginModule</code> should not be ignored).
   * @throws FailedLoginException if the authentication fails. <p>
   */

  public boolean login() throws LoginException {
    switch (status) {
      case UNINITIALIZED:
      default:
        throw new LoginException("The login module is not initialized");
      case INITIALIZED:
      case AUTHENTICATED:

        if (token && !nullStream) {
          throw new LoginException
              ("if keyStoreType is " + P11KEYSTORE +
                  " then keyStoreURL must be " + NONE);
        }

        if (token && privateKeyPasswordURL != null) {
          throw new LoginException
              ("if keyStoreType is " + P11KEYSTORE +
                  " then privateKeyPasswordURL must not be specified");
        }

        if (protectedPath &&
            (keyStorePasswordURL != null ||
                privateKeyPasswordURL != null)) {
          throw new LoginException
              ("if protected is true then keyStorePasswordURL and " +
                  "privateKeyPasswordURL must not be specified");
        }

        // get relevant alias and password info

        if (protectedPath) {
          getAliasAndPasswords(PROTECTED_PATH);
        } else if (token) {
          getAliasAndPasswords(TOKEN);
        } else {
          getAliasAndPasswords(NORMAL);
        }

        // log into KeyStore to retrieve data,
        // then clear passwords

        try {
          getKeyStoreInfo();
        } finally {
          if (privateKeyPassword != null &&
              privateKeyPassword != keyStorePassword) {
            Arrays.fill(privateKeyPassword, '\0');
            privateKeyPassword = null;
          }
          if (keyStorePassword != null) {
            Arrays.fill(keyStorePassword, '\0');
            keyStorePassword = null;
          }
        }
        status = AUTHENTICATED;
        return true;
      case LOGGED_IN:
        return true;
    }
  }

  /**
   * Get the alias and passwords to use for looking up in the KeyStore.
   */
  @SuppressWarnings("fallthrough")
  private void getAliasAndPasswords(int env) throws LoginException {
    if (callbackHandler == null) {

      // No callback handler - check for alias and password options

      switch (env) {
        case PROTECTED_PATH:
          checkAlias();
          break;
        case TOKEN:
          checkAlias();
          checkStorePass();
          break;
        case NORMAL:
          checkAlias();
          checkStorePass();
          checkKeyPass();
          break;
      }

    } else {

      // Callback handler available - prompt for alias and passwords

      NameCallback aliasCallback;
      if (keyStoreAlias == null || keyStoreAlias.length() == 0) {
        aliasCallback = new NameCallback(
            rb.getString("Keystore.alias."));
      } else {
        aliasCallback =
            new NameCallback(rb.getString("Keystore.alias."),
                keyStoreAlias);
      }

      PasswordCallback storePassCallback = null;
      PasswordCallback keyPassCallback = null;

      switch (env) {
        case PROTECTED_PATH:
          break;
        case NORMAL:
          keyPassCallback = new PasswordCallback
              (rb.getString("Private.key.password.optional."), false);
          // fall thru
        case TOKEN:
          storePassCallback = new PasswordCallback
              (rb.getString("Keystore.password."), false);
          break;
      }
      prompt(aliasCallback, storePassCallback, keyPassCallback);
    }

    if (debug) {
      debugPrint("alias=" + keyStoreAlias);
    }
  }

  private void checkAlias() throws LoginException {
    if (keyStoreAlias == null) {
      throw new LoginException
          ("Need to specify an alias option to use " +
              "KeyStoreLoginModule non-interactively.");
    }
  }

  private void checkStorePass() throws LoginException {
    if (keyStorePasswordURL == null) {
      throw new LoginException
          ("Need to specify keyStorePasswordURL option to use " +
              "KeyStoreLoginModule non-interactively.");
    }
    InputStream in = null;
    try {
      in = new URL(keyStorePasswordURL).openStream();
      keyStorePassword = Password.readPassword(in);
    } catch (IOException e) {
      LoginException le = new LoginException
          ("Problem accessing keystore password \"" +
              keyStorePasswordURL + "\"");
      le.initCause(e);
      throw le;
    } finally {
      if (in != null) {
        try {
          in.close();
        } catch (IOException ioe) {
          LoginException le = new LoginException(
              "Problem closing the keystore password stream");
          le.initCause(ioe);
          throw le;
        }
      }
    }
  }

  private void checkKeyPass() throws LoginException {
    if (privateKeyPasswordURL == null) {
      privateKeyPassword = keyStorePassword;
    } else {
      InputStream in = null;
      try {
        in = new URL(privateKeyPasswordURL).openStream();
        privateKeyPassword = Password.readPassword(in);
      } catch (IOException e) {
        LoginException le = new LoginException
            ("Problem accessing private key password \"" +
                privateKeyPasswordURL + "\"");
        le.initCause(e);
        throw le;
      } finally {
        if (in != null) {
          try {
            in.close();
          } catch (IOException ioe) {
            LoginException le = new LoginException(
                "Problem closing the private key password stream");
            le.initCause(ioe);
            throw le;
          }
        }
      }
    }
  }

  private void prompt(NameCallback aliasCallback,
      PasswordCallback storePassCallback,
      PasswordCallback keyPassCallback)
      throws LoginException {

    if (storePassCallback == null) {

      // only prompt for alias

      try {
        callbackHandler.handle(
            new Callback[]{
                bannerCallback, aliasCallback, confirmationCallback
            });
      } catch (IOException e) {
        LoginException le = new LoginException
            ("Problem retrieving keystore alias");
        le.initCause(e);
        throw le;
      } catch (UnsupportedCallbackException e) {
        throw new LoginException(
            "Error: " + e.getCallback().toString() +
                " is not available to retrieve authentication " +
                " information from the user");
      }

      int confirmationResult = confirmationCallback.getSelectedIndex();

      if (confirmationResult == ConfirmationCallback.CANCEL) {
        throw new LoginException("Login cancelled");
      }

      saveAlias(aliasCallback);

    } else if (keyPassCallback == null) {

      // prompt for alias and key store password

      try {
        callbackHandler.handle(
            new Callback[]{
                bannerCallback, aliasCallback,
                storePassCallback, confirmationCallback
            });
      } catch (IOException e) {
        LoginException le = new LoginException
            ("Problem retrieving keystore alias and password");
        le.initCause(e);
        throw le;
      } catch (UnsupportedCallbackException e) {
        throw new LoginException(
            "Error: " + e.getCallback().toString() +
                " is not available to retrieve authentication " +
                " information from the user");
      }

      int confirmationResult = confirmationCallback.getSelectedIndex();

      if (confirmationResult == ConfirmationCallback.CANCEL) {
        throw new LoginException("Login cancelled");
      }

      saveAlias(aliasCallback);
      saveStorePass(storePassCallback);

    } else {

      // prompt for alias, key store password, and key password

      try {
        callbackHandler.handle(
            new Callback[]{
                bannerCallback, aliasCallback,
                storePassCallback, keyPassCallback,
                confirmationCallback
            });
      } catch (IOException e) {
        LoginException le = new LoginException
            ("Problem retrieving keystore alias and passwords");
        le.initCause(e);
        throw le;
      } catch (UnsupportedCallbackException e) {
        throw new LoginException(
            "Error: " + e.getCallback().toString() +
                " is not available to retrieve authentication " +
                " information from the user");
      }

      int confirmationResult = confirmationCallback.getSelectedIndex();

      if (confirmationResult == ConfirmationCallback.CANCEL) {
        throw new LoginException("Login cancelled");
      }

      saveAlias(aliasCallback);
      saveStorePass(storePassCallback);
      saveKeyPass(keyPassCallback);
    }
  }

  private void saveAlias(NameCallback cb) {
    keyStoreAlias = cb.getName();
  }

  private void saveStorePass(PasswordCallback c) {
    keyStorePassword = c.getPassword();
    if (keyStorePassword == null) {
            /* Treat a NULL password as an empty password */
      keyStorePassword = new char[0];
    }
    c.clearPassword();
  }

  private void saveKeyPass(PasswordCallback c) {
    privateKeyPassword = c.getPassword();
    if (privateKeyPassword == null || privateKeyPassword.length == 0) {
            /*
             * Use keystore password if no private key password is
             * specified.
             */
      privateKeyPassword = keyStorePassword;
    }
    c.clearPassword();
  }

  /**
   * Get the credentials from the KeyStore.
   */
  private void getKeyStoreInfo() throws LoginException {

        /* Get KeyStore instance */
    try {
      if (keyStoreProvider == null) {
        keyStore = KeyStore.getInstance(keyStoreType);
      } else {
        keyStore =
            KeyStore.getInstance(keyStoreType, keyStoreProvider);
      }
    } catch (KeyStoreException e) {
      LoginException le = new LoginException
          ("The specified keystore type was not available");
      le.initCause(e);
      throw le;
    } catch (NoSuchProviderException e) {
      LoginException le = new LoginException
          ("The specified keystore provider was not available");
      le.initCause(e);
      throw le;
    }

        /* Load KeyStore contents from file */
    InputStream in = null;
    try {
      if (nullStream) {
        // if using protected auth path, keyStorePassword will be null
        keyStore.load(null, keyStorePassword);
      } else {
        in = new URL(keyStoreURL).openStream();
        keyStore.load(in, keyStorePassword);
      }
    } catch (MalformedURLException e) {
      LoginException le = new LoginException
          ("Incorrect keyStoreURL option");
      le.initCause(e);
      throw le;
    } catch (GeneralSecurityException e) {
      LoginException le = new LoginException
          ("Error initializing keystore");
      le.initCause(e);
      throw le;
    } catch (IOException e) {
      LoginException le = new LoginException
          ("Error initializing keystore");
      le.initCause(e);
      throw le;
    } finally {
      if (in != null) {
        try {
          in.close();
        } catch (IOException ioe) {
          LoginException le = new LoginException
              ("Error initializing keystore");
          le.initCause(ioe);
          throw le;
        }
      }
    }

        /* Get certificate chain and create a certificate path */
    try {
      fromKeyStore =
          keyStore.getCertificateChain(keyStoreAlias);
      if (fromKeyStore == null
          || fromKeyStore.length == 0
          || !(fromKeyStore[0] instanceof X509Certificate)) {
        throw new FailedLoginException(
            "Unable to find X.509 certificate chain in keystore");
      } else {
        LinkedList<Certificate> certList = new LinkedList<>();
        for (int i = 0; i < fromKeyStore.length; i++) {
          certList.add(fromKeyStore[i]);
        }
        CertificateFactory certF =
            CertificateFactory.getInstance("X.509");
        certP =
            certF.generateCertPath(certList);
      }
    } catch (KeyStoreException e) {
      LoginException le = new LoginException("Error using keystore");
      le.initCause(e);
      throw le;
    } catch (CertificateException ce) {
      LoginException le = new LoginException
          ("Error: X.509 Certificate type unavailable");
      le.initCause(ce);
      throw le;
    }

        /* Get principal and keys */
    try {
      X509Certificate certificate = (X509Certificate) fromKeyStore[0];
      principal = new javax.security.auth.x500.X500Principal
          (certificate.getSubjectDN().getName());

      // if token, privateKeyPassword will be null
      Key privateKey = keyStore.getKey(keyStoreAlias, privateKeyPassword);
      if (privateKey == null
          || !(privateKey instanceof PrivateKey)) {
        throw new FailedLoginException(
            "Unable to recover key from keystore");
      }

      privateCredential = new X500PrivateCredential(
          certificate, (PrivateKey) privateKey, keyStoreAlias);
    } catch (KeyStoreException e) {
      LoginException le = new LoginException("Error using keystore");
      le.initCause(e);
      throw le;
    } catch (NoSuchAlgorithmException e) {
      LoginException le = new LoginException("Error using keystore");
      le.initCause(e);
      throw le;
    } catch (UnrecoverableKeyException e) {
      FailedLoginException fle = new FailedLoginException
          ("Unable to recover key from keystore");
      fle.initCause(e);
      throw fle;
    }
    if (debug) {
      debugPrint("principal=" + principal +
          "\n certificate="
          + privateCredential.getCertificate() +
          "\n alias =" + privateCredential.getAlias());
    }
  }

  /**
   * Abstract method to commit the authentication process (phase 2).
   *
   * <p> This method is called if the LoginContext's
   * overall authentication succeeded
   * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
   * succeeded).
   *
   * <p> If this LoginModule's own authentication attempt
   * succeeded (checked by retrieving the private state saved by the
   * <code>login</code> method), then this method associates a
   * <code>X500Principal</code> for the subject distinguished name of the
   * first certificate in the alias's credentials in the subject's
   * principals,the alias's certificate path in the subject's public
   * credentials, and a<code>X500PrivateCredential</code> whose certificate
   * is the first  certificate in the alias's certificate path and whose
   * private key is the alias's private key in the subject's private
   * credentials.  If this LoginModule's own
   * authentication attempted failed, then this method removes
   * any state that was originally saved.
   *
   * <p>
   *
   * @return true if this LoginModule's own login and commit attempts succeeded, or false otherwise.
   * @throws LoginException if the commit fails
   */

  public boolean commit() throws LoginException {
    switch (status) {
      case UNINITIALIZED:
      default:
        throw new LoginException("The login module is not initialized");
      case INITIALIZED:
        logoutInternal();
        throw new LoginException("Authentication failed");
      case AUTHENTICATED:
        if (commitInternal()) {
          return true;
        } else {
          logoutInternal();
          throw new LoginException("Unable to retrieve certificates");
        }
      case LOGGED_IN:
        return true;
    }
  }

  private boolean commitInternal() throws LoginException {
        /* If the subject is not readonly add to the principal and credentials
         * set; otherwise just return true
         */
    if (subject.isReadOnly()) {
      throw new LoginException("Subject is set readonly");
    } else {
      subject.getPrincipals().add(principal);
      subject.getPublicCredentials().add(certP);
      subject.getPrivateCredentials().add(privateCredential);
      status = LOGGED_IN;
      return true;
    }
  }

  /**
   * <p> This method is called if the LoginContext's
   * overall authentication failed.
   * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
   * did not succeed).
   *
   * <p> If this LoginModule's own authentication attempt
   * succeeded (checked by retrieving the private state saved by the
   * <code>login</code> and <code>commit</code> methods),
   * then this method cleans up any state that was originally saved.
   *
   * <p> If the loaded KeyStore's provider extends
   * <code>java.security.AuthProvider</code>,
   * then the provider's <code>logout</code> method is invoked.
   *
   * <p>
   *
   * @return false if this LoginModule's own login and/or commit attempts failed, and true
   * otherwise.
   * @throws LoginException if the abort fails.
   */

  public boolean abort() throws LoginException {
    switch (status) {
      case UNINITIALIZED:
      default:
        return false;
      case INITIALIZED:
        return false;
      case AUTHENTICATED:
        logoutInternal();
        return true;
      case LOGGED_IN:
        logoutInternal();
        return true;
    }
  }

  /**
   * Logout a user.
   *
   * <p> This method removes the Principals, public credentials and the
   * private credentials that were added by the <code>commit</code> method.
   *
   * <p> If the loaded KeyStore's provider extends
   * <code>java.security.AuthProvider</code>,
   * then the provider's <code>logout</code> method is invoked.
   *
   * <p>
   *
   * @return true in all cases since this <code>LoginModule</code> should not be ignored.
   * @throws LoginException if the logout fails.
   */

  public boolean logout() throws LoginException {
    if (debug) {
      debugPrint("Entering logout " + status);
    }
    switch (status) {
      case UNINITIALIZED:
        throw new LoginException
            ("The login module is not initialized");
      case INITIALIZED:
      case AUTHENTICATED:
      default:
        // impossible for LoginModule to be in AUTHENTICATED
        // state
        // assert status != AUTHENTICATED;
        return false;
      case LOGGED_IN:
        logoutInternal();
        return true;
    }
  }

  private void logoutInternal() throws LoginException {
    if (debug) {
      debugPrint("Entering logoutInternal");
    }

    // assumption is that KeyStore.load did a login -
    // perform explicit logout if possible
    LoginException logoutException = null;
    Provider provider = keyStore.getProvider();
    if (provider instanceof AuthProvider) {
      AuthProvider ap = (AuthProvider) provider;
      try {
        ap.logout();
        if (debug) {
          debugPrint("logged out of KeyStore AuthProvider");
        }
      } catch (LoginException le) {
        // save but continue below
        logoutException = le;
      }
    }

    if (subject.isReadOnly()) {
      // attempt to destroy the private credential
      // even if the Subject is read-only
      principal = null;
      certP = null;
      status = INITIALIZED;
      // destroy the private credential
      Iterator<Object> it = subject.getPrivateCredentials().iterator();
      while (it.hasNext()) {
        Object obj = it.next();
        if (privateCredential.equals(obj)) {
          privateCredential = null;
          try {
            ((Destroyable) obj).destroy();
            if (debug) {
              debugPrint("Destroyed private credential, " +
                  obj.getClass().getName());
            }
            break;
          } catch (DestroyFailedException dfe) {
            LoginException le = new LoginException
                ("Unable to destroy private credential, "
                    + obj.getClass().getName());
            le.initCause(dfe);
            throw le;
          }
        }
      }

      // throw an exception because we can not remove
      // the principal and public credential from this
      // read-only Subject
      throw new LoginException
          ("Unable to remove Principal ("
              + "X500Principal "
              + ") and public credential (certificatepath) "
              + "from read-only Subject");
    }
    if (principal != null) {
      subject.getPrincipals().remove(principal);
      principal = null;
    }
    if (certP != null) {
      subject.getPublicCredentials().remove(certP);
      certP = null;
    }
    if (privateCredential != null) {
      subject.getPrivateCredentials().remove(privateCredential);
      privateCredential = null;
    }

    // throw pending logout exception if there is one
    if (logoutException != null) {
      throw logoutException;
    }
    status = INITIALIZED;
  }

  private void debugPrint(String message) {
    // we should switch to logging API
    if (message == null) {
      System.err.println();
    } else {
      System.err.println("Debug KeyStoreLoginModule: " + message);
    }
  }
}
